這系列無障礙的鐵人賽文章,實踐的內容主要是根據 W3C:WAI-ARIA 的實踐,從設計模式及組件(Design Patterns and Widgets)裡面挑選最想嘗試的,如果有朋友想瞭解全部 Widget 該怎麼實作及其規範,歡迎自行爬規範內容,也許我們可以討論一下;若以下文章內容理解有任何錯誤,請多指教~
(圖片來源:unDraw)
3.5 Button
A button is a widget that enables users to trigger an action or event, such as submitting a form, opening a dialog, canceling an action, or performing a delete operation. A common convention for informing users that a button launches a dialog is to append "…" (ellipsis) to the button label, e.g., "Save as…"
按鈕,在一個網站當中,可以說無所不在。它讓使用者可以藉由點擊送出表單、打開對話視窗、取消行為與刪除某操作。最常用的做法是點擊按鈕之後,開啟一個視窗後去做一些事,比如說:存檔、列印...等。
<div>
或 <button>
製作按鈕的差異外觀透過 CSS 都能做到一樣的樣式設定,原生的按鈕元素已具備許多方便鍵盤及螢幕閱讀器操作的特性,以下來介紹有哪些重要觀念:
<div>
:需要加上 tablindex="0"
,告訴鍵盤它是可以被操作的。<button>
:原生的按鈕元素,在使用者用鍵盤底選 tab 時就能聚焦。div 即使加上 tabindex
,變成可聚焦之元素,div 在語義上只是代表一個群組的集合,而 button 卻是代表一個可點按的按鍵,若我們同樣製作一個「註冊」按鈕,使用 VoiceOver 讀出:
<div>
:念出「註冊 group」,只表達群組,無法推敲更深層的意義。<button>
:念出「註冊 button」,不會造成混淆。假設我們使用 JS 在兩個元素加上監聽,當點擊事件發生時,就 alert 一個視窗,告訴你他被點擊了。
anyButton.addEventListener('click', function () {
// doSomething(); => 原生按鈕可以被鍵盤的 Enter 正常觸發
})
anyDiv.addEventListener('click', function () {
// doSomething(); => 假裝的按鈕需要監聽 Keycode
})
<div>
:使用滑鼠點擊,正常觸發。透過鍵盤 tab 選擇,再按下 enter,無法反應,需要額外新增的監聽事件,去聽 keycode 是否按下 enter 鍵、空白鍵。<button>
:使用滑鼠點擊,正常觸發。透過鍵盤 tab 選擇,再按下 enter,正常觸發!同樣為 div 與 button 加上 disabled 屬性,以及樣式:
<div class="fancy" disabled></div>
<button class="fancy" disabled></button>
.fancy[disabled] {
background-color: gray;
}
那麼是否可被關注的狀態就該因為 disabled
而被取消,換句話說,這時我們使用 tab 去選網頁上的內容,disabled 屬性的元素是不該被選取到的,會從 focus order 移除,否則將造成使用者非常混亂。
<div>
:依然可以 focus。<button>
:無法選取。來介紹一下,如果我們要做按鈕組件的一些應用,在 WAI-ARIA 有哪些注意事項。
除了傳統的按鈕組件,WAI-ARIA 還支援另外兩種按鈕類型:
aria-pressed
屬性並指定 true
/ false
。aria-pressed="true"
來表示「靜音」。
aria-label
代表其標題意義)。在上例中,當按下的狀態為 true
時,標籤保持「靜音」 ,這樣螢幕閱讀器就會朗讀「靜音 toggle button pressed」。換句話說,如果設計要求按鈕標籤在按下後,從「靜音」更改為「未靜音」這種文字內容更新,則不再需要使用 aria-pressed
屬性,念出來會混淆視聽。Menu Button
會和另一個設計模式 Menu
/ Menu Bar
一起應用,與 aria-haspopup
屬性通力合作。「按鈕」的行為表現和「超連結」不一樣,它們的樣式與角色模型也許都能符合功能要的需求。但是有很多作法是讓有像超連結樣式的元素,行為表現得像按鈕一樣,這時在元素上加上
role="button"
可以幫助輔助科技瞭解組件是有什麼功能性。
Alt + U
當作觸發按鈕的快捷鍵,可以移動到上一列的話,焦點就移動到上一列。Alt + U
快速鍵,焦點無法超出清單的範圍,一樣保持在 list 上。button
,如果不是原生 <button>
,應該要加上 role="button"
。aria-label="名稱"
或是 aria-labelledby="某元素id"
。aria-describedby="某元素id"
。aria-disabled="true"
,提醒一下,只有寫 aria-disabled
也是 true
唷。aria-pressed
屬性,值是 true
或 false
。今天想引用 WAI-ARIA Practices 1.1 示範的例子來說明最簡單的按鈕概念,之後銜接 Menu Button
來做一些 RWD 常用的漢堡按鈕的話,也會比較清楚、輕鬆。
以下範例是使用兩個非原生具有 button 語義的標籤來實作按鈕,分別是<div>
、<a>
,要做的事情是「列印」與「靜音」,因為原範例 svg 路徑的關係,微調一下 JS 寫法。
希望能做到:
<section>
<p>這是一個 div 按鈕,執行列印功能</p>
<div tabindex="0"
role="button"
id="action">
Print Page
</div>
</section>
<section>
<p>這是一個 a 按鈕,切換靜音與聲音功能</p>
<a tabindex="0"
role="button"
id="toggle"
aria-pressed="false">
Mute
<i class="fas fa-volume-up"></i>
</a>
</section>
<button>
:
role="button"
。tab
鍵 focus 的設定, tabindex="0"
。<a>
,功能需要切換兩種不同的狀態,所以加上 aria-pressed
屬性,值是 false
代表按下之前是有聲音的,按下之後需要關閉聲音,除了樣式要區隔以外,將 aria-pressed
設為 true
。[role="button"] {
display: inline-block;
position: relative;
padding: 0.4em 0.7em;
border: 1px solid hsl(213, 71%, 49%);
border-radius: 5px;
box-shadow: 0 1px 2px hsl(216, 27%, 55%);
color: #fff;
text-shadow: 0 -1px 1px hsl(216, 27%, 25%);
background-color: hsl(216, 82%, 51%);
background-image: linear-gradient(to bottom, hsl(216, 82%, 53%), hsl(216, 82%, 47%));
}
[role="button"]:hover {
border-color: hsl(213, 71%, 29%);
background-color: hsl(216, 82%, 31%);
background-image: linear-gradient(to bottom, hsl(216, 82%, 33%), hsl(216, 82%, 27%));
cursor: default;
}
[role="button"]:focus {
outline: none;
}
[role="button"]:focus::before {
position: absolute;
z-index: -1;
/* button border width - outline width - offset */
top: calc(-1px - 3px - 3px);
right: calc(-1px - 3px - 3px);
bottom: calc(-1px - 3px - 3px);
left: calc(-1px - 3px - 3px);
border: 3px solid hsl(213, 71%, 49%);
/* button border radius + outline width + offset */
border-radius: calc(5px + 3px + 3px);
content: '';
}
[role="button"]:active {
border-color: hsl(213, 71%, 49%);
background-color: hsl(216, 82%, 31%);
background-image: linear-gradient(to bottom, hsl(216, 82%, 53%), hsl(216, 82%, 47%));
box-shadow: inset 0 3px 5px 1px hsl(216, 82%, 30%);
}
[role="button"][aria-pressed] {
/* 這裡只是幫有需要切換狀態的範例加上新的顏色(紫色)做為區別 */
border-color: hsl(261, 71%, 49%);
box-shadow: 0 1px 2px hsl(261, 27%, 55%);
text-shadow: 0 -1px 1px hsl(261, 27%, 25%);
background-color: hsl(261, 82%, 51%);
background-image: linear-gradient(to bottom, hsl(261, 82%, 53%), hsl(261, 82%, 47%));
}
[role="button"][aria-pressed]:hover {
/* 這裡只是幫有需要切換狀態的範例加上新的顏色(紫色)做為區別 */
border-color: hsl(261, 71%, 29%);
background-color: hsl(261, 82%, 31%);
background-image: linear-gradient(to bottom, hsl(261, 82%, 33%), hsl(261, 82%, 27%));
}
[role="button"][aria-pressed]:focus::before {
/* 這裡只是幫有需要切換狀態的範例加上新的顏色(紫色)做為區別 */
border-color: hsl(261, 71%, 49%);
}
/* 開始為狀態加上樣式 */
[role="button"][aria-pressed="true"] {
padding-top: 0.5em;
padding-bottom: 0.3em;
border-color: hsl(261, 71%, 49%);
background-color: hsl(261, 82%, 31%);
background-image: linear-gradient(to bottom, hsl(261, 82%, 63%), hsl(261, 82%, 57%));
box-shadow: inset 0 3px 5px 1px hsl(261, 82%, 30%);
}
[role="button"][aria-pressed="true"]:hover {
border-color: hsl(261, 71%, 49%);
background-color: hsl(261, 82%, 31%);
background-image: linear-gradient(to bottom, hsl(261, 82%, 43%), hsl(261, 82%, 37%));
box-shadow: inset 0 3px 5px 1px hsl(261, 82%, 20%);
}
:hover
:active
:focus
[role="button"][aria-pressed="true"]
。最後,codepen 在這裏,快來使用鍵盤操作看看吧!結論是,除了 <button>
外,還是可以使用 <div>
或是 <a>
來製作一個按鈕,不過需要幫忙補充語義及 JS 要做的事情都比較多(監聽各種 keycode,注意事項請參考上文),大家就自行斟酌囉。